iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0

這篇文章將帶你一步步建立一個自動化工作流程,每月自動從 IMDB Top 250 排行榜中隨機挑選一部電影,並將推薦訊息發送到你的 Discord 頻道。讓我們開始吧

流程總覽

  1. 定時觸發:設定在每個月的固定時間自動啟動

  2. 抓取網頁:從 IMDB Top 250 的官方頁面撈取電影榜單資料

  3. 解析資料:從網頁原始碼中提取出結構化的電影清單

  4. 隨機挑選:從 250 部電影中隨機選出 1 部

  5. 發送通知:將選出的電影資訊格式化後,透過 Webhook 發送到指定的 Discord 頻道

workflow

步驟一:建立排程觸發器

  • 來到儀表板,新增一個流程「Create Workflow」

    image 0.png

  • 初始節點選擇排程「On a schedule」

    image 1.png

  • 設定為每個月的 1 號上午 9 點觸發

    image 2.png

  • 接下來到「設定」裡面調整時區

    image 3.png

  • 把時區的「Timezone」設定為台灣時間

    image 4.png

步驟二:抓取 IMDB 網頁資料

  • 下個節點選擇「HTTP Request」

    image 5.png

  • 方法為「GET」,網址如下

    https://www.imdb.com/chart/top/
    
    • 接著把「Send Headers」切換打開,填入底下欄位

      • Name:User-Agent

      • Value:

        Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
        

    image 6.png

步驟三:解析電影資料

  • 下個節點選擇「HTML」的「Extract HTML Content」

    image 7.png

  • 資料填寫如下:

    • Key:movie_json_ld

    • CSS Selector:script[type="application/ld+json"]

    • Return Value:HTML

    image 8.png

步驟四:轉換資料格式

  • 下個節點選擇「Code」來轉換資料格式

    image 9.png

  • 程式碼填入底下的內容

    try {
      const movieData = JSON.parse($json.movie_json_ld);
    
      // 提取電影清單
      const movies = movieData.itemListElement.map((item) => {
        const movie = item.item;
    
        return {
          // 基本資訊
          title: movie.name,
          alternateName: movie.alternateName,
          description: movie.description,
          url: movie.url,
    
          // 評分資訊
          rating: {
            value: movie.aggregateRating.ratingValue,
            count: movie.aggregateRating.ratingCount,
            bestRating: movie.aggregateRating.bestRating,
            worstRating: movie.aggregateRating.worstRating,
          },
    
          // 其他資訊
          contentRating: movie.contentRating,
          genre: movie.genre,
          duration: movie.duration,
          image: movie.image,
    
          // 從 URL 提取 IMDB ID
          imdbId: movie.url.match(/tt\d+/)[0],
    
          // 將時長轉換為分鐘數
          durationMinutes: movie.duration
            ? parseInt(movie.duration.match(/(\d+)H/)?.[1] || 0) * 60 +
              parseInt(movie.duration.match(/(\d+)M/)?.[1] || 0)
            : null,
        };
      });
    
      return {
        totalMovies: movies.length,
        movies: movies,
        metadata: {
          source: "IMDB Top 250",
          extractedAt: new Date().toISOString(),
          originalUrl: movieData.url,
        },
      };
    } catch (error) {
      // 錯誤處理
      return {
        error: "解析失敗",
        details: error.message,
        originalData: $json,
      };
    }
    

步驟五:隨機挑選一部電影

現在我們有了完整的電影清單,接著就是從中隨機選 1 部

  • 下個節點還是選擇「Code」,程式碼填寫如下

    // 取得上一個節點傳來的資料
    const inputData = $input.item.json;
    
    // 從資料中取得電影列表陣列
    const movies = inputData.movies;
    
    // 產生一個隨機索引值,範圍是 0 到 (電影總數 - 1)
    const randomIndex = Math.floor(Math.random() * movies.length);
    
    // 根據隨機索引值,從陣列中挑選出一部電影
    const randomMovie = movies[randomIndex];
    
    // 回傳這部隨機挑選的電影
    return randomMovie;
    

步驟六:發送至 Discord

  • 下個節點選擇 Discord 的「Send a message」來傳送訊息

    image 10.png

  • 「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹

    image 11.png

  • 可以在「Embeds」直接把上個節點的屬性抓過來使用

    image 12.png

  • 試跑下去就能在 Discord 看到類似這樣的訊息

    image 13.png

  • 完成後的流程長這樣,也要記得到上方切換為「Active」來啟用哦

    image 14.png

  • 最後也附上完整的 JSON

    {
      "nodes": [
        {
          "parameters": {
            "rule": {
              "interval": [
                {
                  "field": "months",
                  "triggerAtHour": 9
                }
              ]
            }
          },
          "type": "n8n-nodes-base.scheduleTrigger",
          "typeVersion": 1.2,
          "position": [0, 0],
          "id": "1422463c-1086-4e96-8ee7-0c9d8a84a286",
          "name": "Schedule Trigger"
        },
        {
          "parameters": {
            "url": "https://www.imdb.com/chart/top/",
            "sendHeaders": true,
            "headerParameters": {
              "parameters": [
                {
                  "name": "User-Agent",
                  "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
                }
              ]
            },
            "options": {}
          },
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [220, 0],
          "id": "d1f56f65-af3f-471d-a0dd-021e0b5fcda5",
          "name": "HTTP Request"
        },
        {
          "parameters": {
            "operation": "extractHtmlContent",
            "extractionValues": {
              "values": [
                {
                  "key": "movie_json_ld",
                  "cssSelector": "script[type=\"application/ld+json\"]",
                  "returnValue": "html"
                }
              ]
            },
            "options": {}
          },
          "type": "n8n-nodes-base.html",
          "typeVersion": 1.2,
          "position": [440, 0],
          "id": "ef987ad1-c339-459b-9dfa-de38b5f1f803",
          "name": "HTML"
        },
        {
          "parameters": {
            "jsCode": "try {\n  const movieData = JSON.parse($json.movie_json_ld);\n  \n  // 提取電影清單\n  const movies = movieData.itemListElement.map(item => {\n    const movie = item.item;\n    \n    return {\n      // 基本資訊\n      title: movie.name,\n      alternateName: movie.alternateName,\n      description: movie.description,\n      url: movie.url,\n      \n      // 評分資訊\n      rating: {\n        value: movie.aggregateRating.ratingValue,\n        count: movie.aggregateRating.ratingCount,\n        bestRating: movie.aggregateRating.bestRating,\n        worstRating: movie.aggregateRating.worstRating\n      },\n      \n      // 其他資訊\n      contentRating: movie.contentRating,\n      genre: movie.genre,\n      duration: movie.duration,\n      image: movie.image,\n      \n      // 從 URL 提取 IMDB ID\n      imdbId: movie.url.match(/tt\\d+/)[0],\n      \n      // 將時長轉換為分鐘數\n      durationMinutes: movie.duration ? \n        parseInt(movie.duration.match(/(\\d+)H/)?.[1] || 0) * 60 + \n        parseInt(movie.duration.match(/(\\d+)M/)?.[1] || 0) : null\n    };\n  });\n  \n  return {\n    totalMovies: movies.length,\n    movies: movies,\n    metadata: {\n      source: \"IMDB Top 250\",\n      extractedAt: new Date().toISOString(),\n      originalUrl: movieData.url\n    }\n  };\n  \n} catch (error) {\n  // 錯誤處理\n  return {\n    error: \"解析失敗\",\n    details: error.message,\n    originalData: $json\n  };\n}\n"
          },
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [660, 0],
          "id": "874e0d62-5759-46e9-9eb6-9044439e5c44",
          "name": "List"
        },
        {
          "parameters": {
            "jsCode": "// 取得上一個節點傳來的資料\nconst inputData = $input.item.json;\n\n// 從資料中取得電影列表陣列\nconst movies = inputData.movies;\n\n// 產生一個隨機索引值,範圍是 0 到 (電影總數 - 1)\nconst randomIndex = Math.floor(Math.random() * movies.length);\n\n// 根據隨機索引值,從陣列中挑選出一部電影\nconst randomMovie = movies[randomIndex];\n\n// 回傳這部隨機挑選的電影\nreturn randomMovie;"
          },
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [880, 0],
          "id": "51d5fd45-a75f-4781-aa3b-0d68eb9141f5",
          "name": "Code"
        },
        {
          "parameters": {
            "authentication": "webhook",
            "options": {},
            "embeds": {
              "values": [
                {
                  "description": "={{ $json.description }}",
                  "title": "={{ $json.title }} | {{ $json.alternateName }}",
                  "url": "={{ $json.url }}",
                  "image": "={{ $json.image }}"
                }
              ]
            }
          },
          "type": "n8n-nodes-base.discord",
          "typeVersion": 2,
          "position": [1100, 0],
          "id": "76b494c2-87db-4c1a-9a10-629036a13382",
          "name": "Discord",
          "webhookId": "[REDACTED_WEBHOOK_ID]"
        }
      ],
      "connections": {
        "Schedule Trigger": {
          "main": [
            [
              {
                "node": "HTTP Request",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "HTTP Request": {
          "main": [
            [
              {
                "node": "HTML",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "HTML": {
          "main": [
            [
              {
                "node": "List",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "List": {
          "main": [
            [
              {
                "node": "Code",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Code": {
          "main": [
            [
              {
                "node": "Discord",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      },
      "pinData": {},
      "meta": {
        "templateCredsSetupCompleted": true,
        "instanceId": "[REDACTED_INSTANCE_ID]"
      }
    }
    

上一篇
[Day19]_股市到價提醒器
下一篇
[Day21]_職缺平台每日整理
系列文
告別重複瑣事: n8n workflow 自動化工作實踐21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言